#include "precompiled/pchkrayserkraw.h"

#include "kraw/krawd3d9fixedmaterial.h"
#include "kraw/kraw3dmodel.h"

#include "kernel/nkernelserver.h"

#include <d3d9.h>
#include <d3dx9math.h>

//-----------------------------------------------------------------------------

//TODO- move to KrawD3D9(Display?)
extern LPDIRECT3DDEVICE9 g_pd3d9Device; // Our rendering device

//structure for exported data, ready to load vertex buffer
struct EXPORTHEADER
{
    uint vertexFormat;
    uint vertexSize;
    uint nVertices;

    float* vertexData;
    uint* indexData;
};

//-----------------------------------------------------------------------------

//declare private class to hold D3D9 Meshes
class KrawD3D9FixedModel
{
public:
    KrawD3D9FixedModel() :
        pVertexBuffer(0),
        primitiveType(D3DPT_TRIANGLELIST),
        nPrimitives(0),
        stride(0),
        fvf(0)
    {
    }

    ~KrawD3D9FixedModel();

    VOID Cleanup();

    HRESULT LoadVertexBuffer( uint nVertices, uint vertexSize, uint vertexFormat, void* pData );
    HRESULT LoadIndexBuffer( uint nIndices, void* pData );

    LPDIRECT3DVERTEXBUFFER9 pVertexBuffer;
    LPDIRECT3DINDEXBUFFER9 pIndexBuffer;
    D3DPRIMITIVETYPE primitiveType;
    UINT nPrimitives;
    UINT stride;
    DWORD fvf;
};

//-----------------------------------------------------------------------------

KrawD3D9FixedModel::~KrawD3D9FixedModel()
{
    this->Cleanup();
}

//-----------------------------------------------------------------------------

HRESULT KrawD3D9FixedModel::LoadVertexBuffer( uint nVertices, uint vertexSize, uint vertexFormat, void* pVertexData )
{
    if ( FAILED( g_pd3d9Device->CreateVertexBuffer( nVertices * vertexSize, 0, vertexFormat,
                                                   D3DPOOL_DEFAULT,
                                                   &this->pVertexBuffer, NULL ) ) )
        return E_FAIL;

    VOID* pVertices;
    if( SUCCEEDED( this->pVertexBuffer->Lock( 0, nVertices * vertexSize, (void**)&pVertices, 0 ) ) )
    {
        memcpy( pVertices, pVertexData, nVertices * vertexSize );
        this->pVertexBuffer->Unlock();
        return S_OK;
    }

    return E_FAIL;
}

//-----------------------------------------------------------------------------

HRESULT KrawD3D9FixedModel::LoadIndexBuffer( uint nIndices, void* pIndexData )
{
    if ( FAILED( g_pd3d9Device->CreateIndexBuffer( nIndices * sizeof(DWORD),
                                                  D3DUSAGE_WRITEONLY, D3DFMT_INDEX32, D3DPOOL_DEFAULT, 
                                                  &this->pIndexBuffer, NULL ) ) )
        return E_FAIL;


    DWORD* pIndices;
    if ( FAILED( this->pIndexBuffer->Lock( 0, nIndices * sizeof(DWORD), (void**) &pIndices, 0 ) ) )
    {
        this->pIndexBuffer->Release();
        this->pIndexBuffer = 0;
        return E_FAIL;
    }

    memcpy( pIndices, pIndexData, nIndices * sizeof(DWORD) );
    this->pIndexBuffer->Unlock();

    return S_OK;
}

//-----------------------------------------------------------------------------

VOID KrawD3D9FixedModel::Cleanup()
{
    if ( this->pVertexBuffer )
    {
        this->pVertexBuffer->Release();
        this->pVertexBuffer = 0;
    }
}

//-----------------------------------------------------------------------------

KrawD3D9FixedMaterial::KrawD3D9FixedMaterial()
{
    //empty
}

//-----------------------------------------------------------------------------

bool KrawD3D9FixedMaterial::GetVertexFormat( Kraw3DModel* pModel, uint& vertexFormat, uint& vertexSize )
{
    if ( !pModel->IsValid() ) return false;

    vertexSize = 0;
    vertexFormat = 0;

    for ( uint iStream = 0; iStream < pModel->nStreams; ++iStream )
    {
        Kraw3DModel::Stream& stream = pModel->streams[iStream];
        switch ( stream.type )
        {
        case Kraw3DModel::Coord:
            vertexFormat |= D3DFVF_XYZ;
            vertexSize += sizeof(nFloat3);
            break;
        //case Kraw3DModel::Normal:
        //    vertexFormat |= D3DFVF_NORMAL;
        //    vertexSize += 3;
        //    break;
        case Kraw3DModel::Diffuse:
            vertexFormat |= D3DFVF_DIFFUSE;
            vertexSize += sizeof(DWORD);//ARGB-packed
            break;
        //case Kraw3DModel::TexCoord0:
        //    vertexFormat |= D3DFVF_TEX0;
        //    //vertexFormat |= D3DFVF_TEXCOORDSIZE2(0)
        //    vertexSize += 2;
        //    break;
        default:
            continue;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------

bool KrawD3D9FixedMaterial::ExportVertices( Kraw3DModel* pModel, uint vertexFormat, uint vertexSize, float*& pVertexData )
{
    //prepare exported data
    pVertexData = (float*) n_new_array( BYTE, pModel->nVertices * vertexSize );

    //convert vertex data
    BYTE* pBufferDst = (BYTE*) pVertexData;
    float* pVertexSrc;
    if ( vertexFormat & D3DFVF_XYZ && pModel->GetStreamData( Kraw3DModel::Coord, &pVertexSrc ) )
    {
        FLOAT* pVertexDst = (FLOAT*) pBufferDst;
        pBufferDst += sizeof(nFloat3);
        for ( uint iVertex = 0; iVertex < pModel->nVertices; ++iVertex ) {
            memcpy( pVertexDst, pVertexSrc, sizeof(nFloat3) );
            pVertexDst += vertexSize / sizeof(FLOAT);
            pVertexSrc += pModel->nComponents;
        }
    }
    //if ( vertexFormat & D3DFVF_NORMAL && model->GetStreamData( Kraw3DModel::Normal, &pVertexSrc ) )
    //{
    //    FLOAT* pVertexDst = (FLOAT*) pBufferDst;
    //    pBufferDst += sizeof(nFloat3);
    //    for ( uint iVertex = 0; iVertex < nVertices; ++iVertex ) {
    //        memcpy( pVertexDst, pVertexSrc, sizeof(nFloat3) );
    //        pVertexDst += vertexSize;
    //        pVertexSrc += model->nComponents;
    //    }
    //}
    if ( vertexFormat & D3DFVF_DIFFUSE && pModel->GetStreamData( Kraw3DModel::Diffuse, &pVertexSrc ) )
    {
        FLOAT* pVertexDst = (FLOAT*) pBufferDst;
        pBufferDst += sizeof(DWORD);
        for ( uint iVertex = 0; iVertex < pModel->nVertices; ++iVertex ) {
            D3DCOLOR* pColorDst = (D3DCOLOR*) pVertexDst;
            *pColorDst = D3DCOLOR_ARGB(int(pVertexSrc[3]*255.f), int(pVertexSrc[0]*255.f), int(pVertexSrc[1]*255.f), int(pVertexSrc[2]*255.f));
            pVertexDst += vertexSize / sizeof(FLOAT);
            pVertexSrc += pModel->nComponents;
        }
    }
    //if ( vertexFormat & D3DFVF_TEX0 && model->GetStreamData( Kraw3DModel::TexCoord0, &pVertexSrc ) )
    //{
    //    FLOAT* pVertexDst = (FLOAT*) pBufferDst;
    //    pBufferDst += sizeof(DWORD);
    //    for ( uint iVertex = 0; iVertex < nVertices; ++iVertex ) {
    //        memcpy( pVertexDst, pVertexSrc, sizeof(nFloat2) );
    //        pVertexDst += vertexSize;
    //        pVertexSrc += model->nComponents;
    //    }
    //}
    //etc. other components go here...
    //END-TODO

    return true;
}

//-----------------------------------------------------------------------------

bool KrawD3D9FixedMaterial::ExportIndices( Kraw3DModel* pModel, uint*& pIndexData )
{
    pIndexData = (uint*) n_new_array( BYTE, pModel->nIndices * sizeof(uint) );

    memcpy( pIndexData, pModel->pIndices, pModel->nIndices * sizeof(uint) );

    return true;
}

//-----------------------------------------------------------------------------

void KrawD3D9FixedMaterial::Delete( float* pVertexData, uint* pIndexData )
{
    if ( pVertexData )
        n_delete_array( pVertexData );
    if ( pIndexData )
        n_delete_array( pIndexData );
}

//-----------------------------------------------------------------------------

void* KrawD3D9FixedMaterial::Load( Kraw3DModel* pModel )
{
    n_assert( pModel );

    KrawD3D9FixedModel* pD3D9Model = n_new( KrawD3D9FixedModel );
    if ( !pD3D9Model ) return 0;

    //extract required fields from exported data
//    EXPORTHEADER* header = (EXPORTHEADER*) pExportedData;
//    FLOAT* pVertexData = (FLOAT*) ( (BYTE*) pExportedData + sizeof(EXPORTHEADER) );

    uint vertexFormat, vertexSize;
    if ( !this->GetVertexFormat( pModel, vertexFormat, vertexSize ) )
        return 0;

    float* pVertexData;
    if ( !this->ExportVertices( pModel, vertexFormat, vertexSize, pVertexData ) )
        return 0;

    uint* pIndexData;
    if ( !this->ExportIndices( pModel, pIndexData ) )
        return 0;

    pD3D9Model->primitiveType = D3DPT_TRIANGLELIST;
    pD3D9Model->nPrimitives = pModel->nVertices / 3;
    pD3D9Model->stride = vertexSize;
    pD3D9Model->fvf = vertexFormat;

    if ( FAILED( pD3D9Model->LoadVertexBuffer( pModel->nVertices, vertexSize, vertexFormat, pVertexData ) ) )
    {
        this->Delete( pVertexData, pIndexData );
        n_delete( pD3D9Model );
        return 0;
    }

    if ( FAILED( pD3D9Model->LoadIndexBuffer( pModel->nIndices, pIndexData ) ) )
    {
        this->Delete( pVertexData, pIndexData );
        n_delete( pD3D9Model );
        return 0;
    }

    this->Delete( pVertexData, pIndexData );
    return (void*) pD3D9Model;
}

//-----------------------------------------------------------------------------

void KrawD3D9FixedMaterial::Unload( void* pModelData )
{
    KrawD3D9FixedModel* pD3D9Model = (KrawD3D9FixedModel*) pModelData;
    pD3D9Model->Cleanup();
    n_delete( pD3D9Model );
}

//-----------------------------------------------------------------------------

void KrawD3D9FixedMaterial::Render( void* pModelData )
{
    n_assert( pModelData );
    KrawD3D9FixedModel* pD3D9Model = (KrawD3D9FixedModel*) pModelData;

    //update the world transform
    this->UpdateTransform();

    //draw model
    g_pd3d9Device->SetStreamSource( 0, pD3D9Model->pVertexBuffer, 0, pD3D9Model->stride );
    g_pd3d9Device->SetFVF( pD3D9Model->fvf );
    g_pd3d9Device->DrawPrimitive( pD3D9Model->primitiveType, 0, pD3D9Model->nPrimitives );
}
